#!/usr/bin/python
# -*- coding: utf-8 -*-
# coding=utf-8

import threading
import _thread
import time
import random
import datetimeDemo as tsLog

# threading并不会使用计算机的多cpu核，仍然是使用的单核进行计算的，所以并不会加快计算速度。https://www.cnblogs.com/stubborn412/p/4033651.html
# GIL 的全称为 Global Interpreter Lock ，意即全局解释器锁。
# 在 Python 语言的主流实现 CPython 中，GIL 是一个货真价实的全局线程锁，在解释器解释执行任何 Python 代码时，都需要先获得这把锁才行，在遇到 I/O 操作时会释放这把锁。
# 如果是纯计算的程序，没有 I/O 操作，解释器会每隔 100 次操作就释放这把锁，让别的线程有机会执行（这个次数可以通过sys.setcheckinterval 来调整）。
# 所以虽然 CPython 的线程库直接封装操作系统的原生线程，但 CPython 进程做为一个整体，同一时间只会有一个获得了 GIL 的线程在跑，其它的线程都处于等待状态等着 GIL 的释放。
# 引入multiprocessing这个多进程标准库，让多进程的 python 程序编写简化到类似多线程的程度，大大减轻了 GIL 带来的不能利用多核的尴尬。

def threadEndAction(threadControlObj):
    if threadControlObj and isinstance(threadControlObj, _thread.LockType):
        # 如果是锁，解锁
        threadControlObj.release()
    elif threadControlObj and isinstance(threadControlObj, threading.Semaphore):
        # 如果是信号量，释放信号
        threadControlObj.release()
    elif threadControlObj and isinstance(threadControlObj, threading.Event):
        # 如果是Event，设置信号量为true
        threadControlObj.set()

def taskA(threadControlObj):
    sleepTime = 1+random.random()
    tsLog.logTs("taskA begin, %.2f" % (sleepTime, ))
    time.sleep(sleepTime)
    tsLog.logTs("taskA end")

    threadEndAction(threadControlObj)

def taskB(threadControlObj):
    sleepTime = 1+random.random()
    tsLog.logTs("taskB begin, %.2f" % (sleepTime, ))
    time.sleep(sleepTime)
    tsLog.logTs("taskB end")

    threadEndAction(threadControlObj)


def taskC(threadControlObj):
    sleepTime = 1+random.random()
    tsLog.logTs("taskC begin, %.2f" % (sleepTime, ))
    time.sleep(sleepTime)
    tsLog.logTs("taskC end")
    
    threadEndAction(threadControlObj)

def taskD(threadControlObj):
    sleepTime = 1+random.random()
    tsLog.logTs("taskD begin, %.2f" % (sleepTime, ))
    time.sleep(sleepTime)
    tsLog.logTs("taskD end")
    
    threadEndAction(threadControlObj)

def lockDemo():
    tsLog.logTs("lockDemo", preNewLineNum=1)

    # 主线程默认是非守护线程的，所以在主线程中创建的线程默认都是非守护线程的，即daemon=False。
    print("current thread daemon:", threading.current_thread().daemon)
    #threading.Timer(1.5, timmerFunc).start()

    # 4个任务ABCD
    # C依赖AB完成
    # D和ABC无关，期望尽量快执行完成所有任务

    tD = threading.Thread(target=taskD, args=(None, ))
    tD.start()

    lA = threading.Lock()
    print("lA", type(lA))
    lA.acquire()
    tA = threading.Thread(target=taskA, args=(lA, ))
    tA.start()

    lB = threading.Lock()
    lB.acquire()
    tB = threading.Thread(target=taskB, args=(lB, ))
    tB.start()

    lA.acquire()
    lB.acquire()
    tC = threading.Thread(target=taskC, args=(None, ))
    tC.start()

    tC.join()
    tD.join()

    tsLog.logTs("Task A B C D end")


def semaphoreDemo():
    # 一个信号量 threading.Semaphore 管理一个内部计数器，acquire( )方法会减少计数器，release( )方法则增加计算器，
    # 计数器的值永远不会小于零，当调用acquire( )时，如果发现该计数器为零，则阻塞线程，直到调用release( ) 方法使计数器增加。

    tsLog.logTs("semaphoreDemo", preNewLineNum=1)
    # 使用信号量控制多线程交互
    # 4个任务ABCD
    # C依赖AB完成
    # D和ABC无关，期望尽量快执行完成所有任务

    sA = threading.Semaphore(1)
    sB = threading.Semaphore(1)

    tD = threading.Thread(target=taskD, args=(None, ))
    tD.start()

    sA.acquire()
    tA = threading.Thread(target=taskA, args=(sA, ))
    tA.start()

    sB.acquire()
    tB = threading.Thread(target=taskB, args=(sB, ))
    tB.start()

    sA.acquire()
    sB.acquire()
    tC = threading.Thread(target=taskC, args=(None, ))
    tC.start()

    tC.join()
    tD.join()

    tsLog.logTs("Task A B C D end")

# https://www.pianshen.com/article/99321562034/
def eventDemo():
    # 事件主要提供了三个方法wait、clear、set
    # 事件处理的机制：全局定义了一个“Flag”，如果“Flag”值为 False，那么当程序执行 event.wait 方法时就会阻塞，如果“Flag”值为True，那么event.wait 方法时便不再阻塞。

    tsLog.logTs("eventDemo", preNewLineNum=1)
    # 使用信号量控制多线程交互
    # 4个任务ABCD
    # C依赖AB完成
    # D和ABC无关，期望尽量快执行完成所有任务
    eA = threading.Event()
    eB = threading.Event()

    tD = threading.Thread(target=taskD, args=(None, ))
    tD.start()

    tA = threading.Thread(target=taskA, args=(eA, ))
    tA.start()

    tB = threading.Thread(target=taskB, args=(eB, ))
    tB.start()

    eA.wait()
    eB.wait()
    tC = threading.Thread(target=taskC, args=(None, ))
    tC.start()

    tC.join()
    tD.join()

    tsLog.logTs("Task A B C D end")

'''
    Condition
    被称为条件变量，可以理解为一种进阶的锁，使用的基本原理如下：
    1、Condition对象的构造函数可以接受一个 Lock / RLock 对象作为参数，如果没有指定，则默认是可重入锁RLock，生成的Condition对象会维护一个Lock / RLock和一个waiting池。
    2、线程调用acquire方法获得Condition对象，这一点和Lock / RLock类似 。
        wait() 方法、notify() 方法、notify_all() 方法都只能在线程调用 acquire 方法之后，线程调用 release 方法之前！
    3、线程调用wait方法时，线程会释放Condition内部的锁并进入blocked状态，同时在waiting池中记录这个线程。
    4、线程调用notify / notify_all 方法时，Condition对象会从waiting池中挑选一个线程，通知其调用acquire 方法尝试取到锁。线程只能通过notify方法唤醒，所以notifyAll的作用在于防止有的线程永远处于沉默状态。
    5、线程调用release方法释放Condition对象
'''
def conditionWait(con, threadFunc):
    con.acquire()

    con.wait()
    threadFunc(con)

    con.release()

def conditionRun(con, threadFunc):
    threadFunc(con)

    con.acquire()
    con.notify_all()
    con.release()

def conditionDemo():
    tsLog.logTs("conditionDemo", preNewLineNum=1)

    # 使用条件变量condition控制多线程交互
    # 4个任务ABCD
    # C和D 依赖 A or B完成
    # 期望尽量快执行完成所有任务

    c4CD = threading.Condition()

    # taskC 和 taskD 在新线程启动，等待 c4CD 的 notify_all 方法被调用
    tC = threading.Thread(target=conditionWait, args=(c4CD, taskC))
    tC.start()

    tD = threading.Thread(target=conditionWait, args=(c4CD, taskD))
    tD.start()

    # taskA 和 taskB 在新线程启动，当任一结束后，都调用 c4CD 的 notify_all
    tA = threading.Thread(target=conditionRun, args=(c4CD, taskA))
    tA.start()

    tB = threading.Thread(target=conditionRun, args=(c4CD, taskB))
    tB.start()

    tC.join()
    tD.join()

    tsLog.logTs("Task A B C D end")


def demo():
    tsLog.logTs("multiThreadDemo", preNewLineNum=2)
    lockDemo()
    semaphoreDemo()
    eventDemo()

    conditionDemo()



if __name__ == '__main__':
    demo()